
/*******************************************************************************
 * Copyleft (c) 2021 Kcode
 *
 * @file    s3c2440_dma.c
 * @brief   ASOC框架下的platform部分，对s3c2440控制芯片的dma(视频数据的传输)接
 *          口进行设置，参考sound\soc\samsung\dma.c
 *
 * @note    核心结构体snd_soc_platform_driver
 *          数据传输实现：
 *              1. 分配DMA buffer
 *              2. 从buffer中取出一个period
 *              3. 启动DMA传输
 *              4. 传输完毕，更新状态(hw_ptr)
 *                 2、3、4这部分主要有：request_irq、触发DMA中断、中断处理
 * @author  K
 * @version 0.0.1
 * @date    2021-08-07
 * @license MulanPSL-1.0
 *
 * 文件修改历史：
 * <时间>       | <版本>    | <作者>  | <描述>
 * 2021-08-07   | v0.0.1    | Kcode   | s3c2440控制芯片的dma接口的设置
 * -----------------------------------------------------------------------------
 ******************************************************************************/


#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/module.h>
    
#include <sound/soc.h>
#include <sound/pcm_params.h>
    
#include <asm/dma.h>
#include <mach/hardware.h>
#include <mach/dma.h>

/*!
 * S3C2440中的每个DMA
 */
#define DMA0_BASE_ADDR  0x4B000000
#define DMA1_BASE_ADDR  0x4B000040
#define DMA2_BASE_ADDR  0x4B000080
#define DMA3_BASE_ADDR  0x4B0000C0

/*!
 * 描绘分配的dma_buffer的属性
 */
typedef struct s3c2440_dma_buffer_info {
    unsigned int buf_max_size; /**< 分配的buffer大小 */
    unsigned int buf_size;      /**< 实际上使用的buffer大小 */
    unsigned int period_size;   /**< 每个period的大小*/
    unsigned int dma_ofs;       /**< 记录使用的偏移地址 */
    unsigned int be_running;    /**< 判断DMA是否在传输数据，0-无，1-有 */
    unsigned int phy_addr;      /**< buffer的物理地址 */
    unsigned int virt_addr;     /**< buffer的虚拟地址 */
}S3C2440_DMA_BUFFER_INFO_S;

/*!
 * 控制芯片的iis传输数据时dma属性设置
 */
static const struct snd_pcm_hardware s_s3c_2440_dma_hardware = {
	.info			= SNDRV_PCM_INFO_INTERLEAVED |
				    SNDRV_PCM_INFO_BLOCK_TRANSFER |
				    SNDRV_PCM_INFO_MMAP |
				    SNDRV_PCM_INFO_MMAP_VALID |
				    SNDRV_PCM_INFO_PAUSE |
				    SNDRV_PCM_INFO_RESUME,  /**< 数据存放格式、排列方式 */
	.formats		= SNDRV_PCM_FMTBIT_S16_LE |
				    SNDRV_PCM_FMTBIT_U16_LE |
				    SNDRV_PCM_FMTBIT_U8 |
				    SNDRV_PCM_FMTBIT_S8,    /**< 音频数据格式 */

    /* 通道数 */
	.channels_min		= 2,
	.channels_max		= 2,

    /* 传输音频数据的信息 */
	.buffer_bytes_max	= 128*1024,
	.period_bytes_min	= PAGE_SIZE,
	.period_bytes_max	= PAGE_SIZE*2,
	.periods_min		= 2,
	.periods_max		= 128,
	.fifo_size		    = 32,
};

/*!
 * DMA操作的相关寄存器
 */
typedef struct s3c_dma_regs {
	unsigned long disrc;
	unsigned long disrcc;
	unsigned long didst;
	unsigned long didstc;
	unsigned long dcon;
	unsigned long dstat;
	unsigned long dcsrc;
	unsigned long dcdst;
	unsigned long dmasktrig;
}S3C_DMA_REG_S;

static volatile S3C_DMA_REG_S *s_dma_regs;

/* 记录分配的dma_buffer信息 */
static S3C2440_DMA_BUFFER_INFO_S s_playback_dma_buf_info;

/*!
 * @brief  加载一个周期的dma_buffer数据
 * @return void
 */
static void load_dma_period(void)
{   
    /* 把源,目的,长度告诉DMA */
    s_dma_regs->disrc = s_playback_dma_buf_info.dma_ofs +\
                        s_playback_dma_buf_info.phy_addr; /* 源的物理地址 */

    /* 源位于AHB总线, 源地址递增 */
    s_dma_regs->disrcc = (0 << 1) | (0 << 0); 

    /* 目的的物理地址 */
    s_dma_regs->didst = 0x55000010;       

    /* 目的位于APB总线, 目的地址不变 */
    s_dma_regs->didstc = (0 << 2) | (1 << 1) | (1 << 0); 

    /* 使能中断,单个传输,硬件触发
     * 注意：由于此驱动程式中，加载数据时为程序手工确定
     *       需要设置bit[22]：1-noreload
     */
    s_dma_regs->dcon = (1 << 31) | (0 << 30) | (1 << 29) | (0 << 28) |\
                        (0 << 27) | (0 << 24) | (1 << 23) | (1 << 22) | (1 << 20) |\
                        (s_playback_dma_buf_info.period_size / 2);
}

/*!
 * @brief  启动DMA
 * @return void
 */
static void s3c2440_dma_start(void)
{
	s_dma_regs->dmasktrig  = (1 << 1);
}

/*!
 * @brief  停止DMA传输
 * @return void
 */
static void s3c2440_dma_stop(void)
{
	s_dma_regs->dmasktrig  &= ~(1 << 1);
}

/*!
 * @brief DMA2的中断处理函数，每传输完一个period触发中断
 * @return irqreturn_t
 */
static irqreturn_t s3c2440_dma2_irq(int irq, void *devid)
{
    struct snd_pcm_substream *substream = devid;

    /*!
     * 更新状态信息
     */
    s_playback_dma_buf_info.dma_ofs += s_playback_dma_buf_info.period_size;
    if (s_playback_dma_buf_info.dma_ofs >= s_playback_dma_buf_info.buf_size)
        s_playback_dma_buf_info.dma_ofs = 0;

    /*!
     * 更新hw_ptr等信息，并且判断：若buffer中无数据，则调用.trigger来停止DMA传输 
     */
    snd_pcm_period_elapsed(substream);

    /*!
     * 判断buffer是否有数据
     */
    if (s_playback_dma_buf_info.be_running == 1) {
        /* 加载下一个period */
        load_dma_period();

        /* 启动DMA传输 */
        s3c2440_dma_start();
    }
    
    return IRQ_HANDLED;
}

/*!
 * @brief DMA接口属性、数据设置、申请中断
 * @return int 0
 */
static int s3c2440_dma_open(struct snd_pcm_substream *substream)
{
    int ret;
	struct snd_pcm_runtime *runtime = substream->runtime;

    /* 设置属性 */
	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
	snd_soc_set_runtime_hwparams(substream, &s_s3c_2440_dma_hardware);

    /* 申请中断 */
    ret = request_irq(IRQ_DMA2, s3c2440_dma2_irq, IRQF_DISABLED, "myalsa for playback", substream);
    if (ret) {
        printk("request_irq error\n!");
        return -EIO;
    }
    
	return 0;
}

static int s3c2440_dma_close(struct snd_pcm_substream *substream)
{
    free_irq(IRQ_DMA2, substream);
	return 0;
}

/*!
 * @brief 根据params参数设置s3c2440控制芯片上的dma接口的寄存器
 * @return int 0
 */
static int s3c2440_dma_hw_params(struct snd_pcm_substream *substream,
	                                            struct snd_pcm_hw_params *params)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	unsigned long totbytes = params_buffer_bytes(params);

    /* 根据params设置DMA */
    snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);

    /* 驱动程序分配一个很大的dma_buffer, 由用户空间的params决定使用多少 */
	runtime->dma_bytes = totbytes;
    s_playback_dma_buf_info.buf_size = totbytes;
    s_playback_dma_buf_info.period_size = params_period_bytes(params);
    
	return 0;
}

/*!
 * @brief DMA传输前的准备工作
 * @return int 0
 */
static int s3c2440_dma_prepare(struct snd_pcm_substream *substream)
{
    /* 准备DMA传输 */

    /* 复位各种状态信息 */
    s_playback_dma_buf_info.dma_ofs = 0;
    s_playback_dma_buf_info.be_running = 0;

    /* 加载第一个period */
    load_dma_period();
    
    return 0;
}

/*!
 * @brief DMA传输的触发工作，根据cmd启动或停止DMA传输
 * @return int 0
 */
static int s3c2440_dma_trigger(struct snd_pcm_substream *substream, int cmd)
{
	int ret = 0;
   
	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
	case SNDRV_PCM_TRIGGER_RESUME:
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
        /* 启动DMA传输 */
        s_playback_dma_buf_info.be_running = 1;
        s3c2440_dma_start();
		break;

	case SNDRV_PCM_TRIGGER_STOP:
	case SNDRV_PCM_TRIGGER_SUSPEND:
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
        /* 停止DMA传输 */
        s_playback_dma_buf_info.be_running = 0;
        s3c2440_dma_stop();
		break;

	default:
		ret = -EINVAL;
		break;
	}


	return ret;
}

/*!
 * @brief  更新DMA传输时的hw_ptr
 * @return snd_pcm_uframes_t 成功：frame的个数
 */
static snd_pcm_uframes_t s3c2440_dma_pointer(struct snd_pcm_substream *substream)
{
	return bytes_to_frames(substream->runtime, s_playback_dma_buf_info.dma_ofs);
}

static struct snd_pcm_ops s3c2440_dma_ops = {
    .open      = s3c2440_dma_open,
    .close     = s3c2440_dma_close,
	.ioctl		= snd_pcm_lib_ioctl,
	.hw_params = s3c2440_dma_hw_params,
	.prepare   = s3c2440_dma_prepare,
	.trigger   = s3c2440_dma_trigger,
	.pointer  = s3c2440_dma_pointer,
};

static u64 dma_mask = DMA_BIT_MASK(32);

/*!
 * @brief  分配一个DMA buffer，用来给驱动程序存储音频数据
 *         DMA buffer实质为一个环形缓冲区，APP负责写，驱动程序负责读
 * @return int 0：成，-ENOMEM：失败
 */
static int s3c2440_dma_new(struct snd_soc_pcm_runtime *rtd)
{
    struct snd_card *card = rtd->card->snd_card;
	struct snd_pcm *pcm = rtd->pcm;

    struct snd_pcm_substream *substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
    struct snd_dma_buffer *buf = &substream->dma_buffer;

    if (!card->dev->dma_mask)
            card->dev->dma_mask = &dma_mask;
    if (!card->dev->coherent_dma_mask)
        card->dev->coherent_dma_mask = DMA_BIT_MASK(32);

    /*!
     * 分配播放时使用的DMA buffer
     */
	if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
        /* 分配 */
        s_playback_dma_buf_info.virt_addr = (unsigned int *)dma_alloc_writecombine(pcm->card->dev, 
                    s_s3c_2440_dma_hardware.buffer_bytes_max, 
                    s_playback_dma_buf_info.phy_addr, GFP_KERNEL);
        if (s_playback_dma_buf_info.virt_addr = NULL) {
            printk("dma_buffer malloc err!\n");
            return -ENOMEM;
        }

        /* 记录信息 */
        s_playback_dma_buf_info.buf_max_size = s_s3c_2440_dma_hardware.buffer_bytes_max;

        /* 把信息告诉ALSA核心层 */
        buf->dev.type = SNDRV_DMA_TYPE_DEV;
        buf->dev.dev = pcm->card->dev;
        buf->private_data = NULL;
        buf->area = (unsigned char *)s_playback_dma_buf_info.virt_addr;
        buf->bytes = s_playback_dma_buf_info.buf_max_size;
	}

    return 0;
}

/*!
 * @brief  释放所分配的buffer
 * @return void
 */
static void s3c2440_dma_free_dma_buffers(struct snd_pcm *pcm)
{
	/*!
	 * 释放用于播放的buffer
	 */
	dma_free_writecombine(pcm->card->dev, s_playback_dma_buf_info.buf_max_size,
			      s_playback_dma_buf_info.virt_addr, s_playback_dma_buf_info.phy_addr);

    s_playback_dma_buf_info.virt_addr = NULL;
    s_playback_dma_buf_info.phy_addr  = NULL;
}

/*!
 * 核心结构体snd_soc_platform_driver
 */
static struct snd_soc_platform_driver s3c2440_dma_platform = {
	.ops		= &s3c2440_dma_ops,
	.pcm_new	= s3c2440_dma_new,
	.pcm_free	= s3c2440_dma_free_dma_buffers,
};

/*!
 * @brief 关闭设备的函数，必须得有，否则程序编译报错
 * @return void
 */
static void s3c2440_dma_dev_release(struct device *dev)
{}

/*!
 * 分配注册一个名为s3c2440_iis_dev的平台设备
 */
static struct platform_device s3c2440_dma_dev = {
    .name    = "s3c2440-dma",
    .id      = -1,
    .dev  = {
        .release = s3c2440_dma_dev_release,
    },
};

/*!
 * @brief  根据名字，连接到对应的平台驱动与设备时所调用的函数
 * @return int
 */
static int s3c2440_dma_probe(struct platform_device *pdev)
{    
    return snd_soc_register_platform(&pdev->dev, &s3c2440_dma_platform);
}

/*!
 * @brief 关闭平台驱动的函数，必须得有，否则程序编译报错
 * @return int
 */
static int s3c2440_dma_remove(struct platform_device *pdev)
{
    snd_soc_unregister_platform(&pdev->dev);
    return 0;
}

/*!
 * 分配注册一个名为s3c2440_dma_dev的平台驱动
 */
struct platform_driver s3c2440_dma_drv = {
    .probe   = s3c2440_dma_probe,
    .remove  = s3c2440_dma_remove,
    .driver  = {
        .name = "s3c2440-dma",
    },
};

/*!
  * @brief 入口函数
  * @return int 0：注册成功，1：注册失败
  */
 static int s3c2440_dma_init(void)
 {
     int ret = 0;

     s_dma_regs = ioremap(DMA2_BASE_ADDR, sizeof(S3C_DMA_REG_S));
     
     ret = platform_device_register(&s3c2440_dma_dev);
     if (ret) {
         printk("Can not register platform s3c2440_dma_dev!\n");
         goto fail;
     }
 
     ret = platform_driver_register(&s3c2440_dma_drv);
     if (ret) {
         printk("Can not register platform s3c2440_dma_drv!\n");
         platform_device_unregister(&s3c2440_dma_dev);
         goto fail;
     }
 
 fail:    
     return ret;
 }
 
 /*!
  * @brief 出口函数
  * @return int，
  */
 static void s3c2440_dma_exit(void)
 {
    iounmap(s_dma_regs);
    platform_driver_unregister(&s3c2440_dma_drv);
    platform_device_unregister(&s3c2440_dma_dev);
 }
  
  /* 修饰 */
  module_init(s3c2440_dma_init);
  module_exit(s3c2440_dma_exit);
  
  /* 协议 */
  MODULE_LICENSE("GPL");

